package httptoken

import (
	// "crypto/hmac"
	// "crypto/sha1"
	"encoding/hex"
	"errors"
	// "fmt"
	// "net/url"
	"strconv"
	// "strings"
	"fmt"
	"sync"
	"time"

	"gitee.com/kobeyyf/apibox/handler"
	// "github.com/dgrijalva/jwt-go"
	"github.com/json-iterator/go"
	"github.com/spf13/viper"
)

const (
	HEADER_NAME_CSRFTOKEN = "Token"
)

var token *Token

type Token struct {
	c           *viper.Viper
	secret      []byte
	expTime     int64
	headerCheck bool

	whiteList map[string]map[string]struct{}

	lock     *sync.Mutex
	dropList map[string]int64
}

func NewTokenSigner(c *viper.Viper) (*Token, error) {
	token = &Token{
		c:    c,
		lock: new(sync.Mutex),
	}
	token.loadWhiteList()
	token.dropList = make(map[string]int64, 50)
	token.secret = []byte(c.GetString("apitoken.secret"))
	if len(token.secret) == 0 {
		return nil, errors.New("miss apitoken.secret")
	}
	token.headerCheck = c.GetBool("apitoken.header")

	token.expTime = c.GetInt64("apitoken.exp")
	go token.clean()
	return token, nil

}

func Signed(value map[string]string) string {
	if token == nil {
		return ""
	}
	return token.Signed(value)
}
func Decode(tokenStr string) (map[string]string, error) {
	if token == nil {
		return map[string]string{}, nil
	}
	return token.Decode(tokenStr)
}
func Drop(tokenStr string) {
	if token == nil {
		return
	}
	token.lock.Lock()
	token.dropList[tokenStr] = time.Now().Unix()
	token.lock.Unlock()
}

func (token *Token) Decode(tokenStr string) (map[string]string, error) {
	tokenBuf, err := hex.DecodeString(tokenStr)
	if err != nil {
		return nil, err
	}

	plainText, _ := lazyDecrypt(tokenBuf, token.secret)
	values := make(map[string]string, 5)
	err = jsoniter.Unmarshal(plainText, &values)
	if err != nil {
		return nil, err
	}
	return values, nil
}

func (token *Token) Signed(value map[string]string) string {

	// if token.expTime > 0 {
	value["_token_exp"] = strconv.FormatInt(time.Now().Unix()+token.expTime, 10)
	// }

	buf, _ := jsoniter.Marshal(value)
	cipherText, _ := lazyEncrypt(buf, token.secret)
	// if err != nil {
	// 	fmt.Println(err)
	// }
	return hex.EncodeToString(cipherText)
}

func (token *Token) Check(c *handler.Context) *handler.Response {
	// 白名单匹配
	appWhitelist, has := token.whiteList[c.Request.FormValue(handler.APPCODE)]
	if has {
		_, has = appWhitelist[c.Request.FormValue(handler.ACTION_NAME)]
		if has {
			return nil
		}
	}

	var tokenStr string
	if token.headerCheck {
		tokenStr = c.Request.Header.Get(HEADER_NAME_CSRFTOKEN)
	} else {
		// token 验证
		tokenStr = c.Request.FormValue("Token")
	}

	if tokenStr == "" {
		return handler.NewErrorResponseWith400(handler.ErrorCode_MissingParameter, "Token")
	}
	token.lock.Lock()
	if _, has := token.dropList[tokenStr]; has {
		token.lock.Unlock()
		return handler.NewErrorResponseWith400("TokenExp", "Token is exp")
	}
	token.lock.Unlock()

	tokenBuf, err := hex.DecodeString(tokenStr)
	if err != nil {
		fmt.Println("decode base64 failed", err)
		return handler.NewErrorResponseWith400(handler.ErrorCode_InvalidParamater, "Token is error")
	}

	plainText, _ := lazyDecrypt(tokenBuf, token.secret)
	values := make(map[string]string, 5)
	err = jsoniter.Unmarshal(plainText, &values)
	if err != nil {
		fmt.Println("unmars json failed", err)
		return handler.NewErrorResponseWith400(handler.ErrorCode_InvalidParamater, "Token is error")
	}

	if token.expTime > 0 {
		exp, _ := strconv.ParseInt(values["_token_exp"], 10, 64)
		if exp < time.Now().Unix() {
			return handler.NewErrorResponseWith400("TokenExp", "Token is exp")
		}
	}

	for k, v := range values {
		c.Request.Form.Set(k, v)
	}

	return nil
}

func (token *Token) loadWhiteList() {
	whitelist := token.c.GetStringMapStringSlice("apitoken.whitelist")

	token.whiteList = make(map[string]map[string]struct{}, len(whitelist))
	for app, actions := range whitelist {
		appWhiteList := make(map[string]struct{}, len(actions))
		token.whiteList[app] = appWhiteList
		for _, action := range actions {
			appWhiteList[action] = struct{}{}
		}
	}
}
